/**
 * @file lcd_drive.c
 * @author cangyu (sky.kirto@qq.com)
 * @brief
 * @version 0.1
 * @date 2024-09-02
 *
 * @copyright Copyright (c) 2024, CorAL. All rights reserved.
 *
 */

/* ==================== [Includes] ========================================== */

#include "lcd_drive_internal.h"

/* ==================== [Defines] =========================================== */

#define TAG "lcd_drive"

/* ==================== [Typedefs] ========================================== */

typedef struct {
    uint16_t original_width;    /*!< 原始宽度 */
    uint16_t original_height;   /*!< 原始高度 */
    uint16_t width;             /*!< 屏幕宽度 */
    uint16_t height;            /*!< 屏幕高度 */
    uint16_t offset_hor;        /*!< 水平偏移 */
    uint16_t offset_ver;        /*!< 垂直偏移 */
    lcd_scr_dir_t dir;          /*!< 屏幕方向 */
} lcd_scr_handle_t;

typedef struct _lcd_drive_t {
    lcd_drive_out_t *out;
    lcd_drive_in_t *in;
    lcd_scr_handle_t scr;
    lcd_scr_controller_t controller;
} lcd_drive_t;

/* ==================== [Static Prototypes] ================================= */

static void controller_drive_reg(void);

/* ==================== [Static Variables] ================================== */

static const lcd_drive_out_t *s_lcd_drive_default[SCR_CONTROLLER_MAX] = {0};

/* ==================== [Macros] ============================================ */

/* ==================== [Global Functions] ================================== */

void lcd_controller_driver_reg(lcd_scr_controller_t controller, const lcd_drive_out_t *out)
{
    s_lcd_drive_default[controller] = out;
}

lcd_t *lcd_drive_create(const lcd_drive_in_t *in)
{
    lcd_drive_t *self = (lcd_drive_t *)xf_malloc(sizeof(lcd_drive_t));
    xf_bzero(self, sizeof(lcd_drive_t));
    self->in = in;

    controller_drive_reg();

    return (lcd_t *)self;
}

xf_err_t lcd_drive_init(lcd_t self, lcd_scr_controller_t controller, uint16_t width, uint16_t height)
{
    lcd_drive_t *lcd = (lcd_drive_t *)self;

    xf_err_t err = XF_OK;

    lcd->scr.dir = SCR_DIR_LRTB;
    lcd->scr.offset_hor = 0;
    lcd->scr.offset_ver = 0;
    lcd->scr.width = width;
    lcd->scr.height = height;
    lcd->scr.original_width = width;
    lcd->scr.original_height = height;
    lcd->controller = controller;
    lcd->out = s_lcd_drive_default[controller];
    err = lcd->out->init(lcd);
    // TODO 判断 err 是否为XF_OK
    err = lcd_set_rotation(self, lcd->scr.dir);
    // TODO 判断 err 是否为XF_OK

    return err;
}

xf_err_t lcd_drive_draw_bitmap(lcd_t self, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *bitmap)
{
    lcd_drive_t *lcd = (lcd_drive_t *)self;
    return lcd->out->draw_bitmap(lcd, x, y, w, h, bitmap);
}

void lcd_drive_offset(lcd_t self, uint16_t *x0, uint16_t *y0, uint16_t *x1, uint16_t *y1)
{
    lcd_drive_t *lcd = (lcd_drive_t *)self;
    uint16_t res_hor = lcd->out->res_hor;
    uint16_t res_ver = lcd->out->res_ver;
    lcd_scr_dir_t dir = lcd->scr.dir;
    lcd_scr_handle_t *lcd_handle = &lcd->scr;
    if (SCR_DIR_MAX < dir) {
        dir >>= 5;
    }
    uint16_t xoffset = 0, yoffset = 0;
    switch (dir) {
    case SCR_DIR_LRTB:
        xoffset = lcd_handle->offset_hor;
        yoffset = lcd_handle->offset_ver;
        break;
    case SCR_DIR_LRBT:
        xoffset = lcd_handle->offset_hor;
        yoffset = res_ver - lcd_handle->offset_ver - lcd_handle->original_height;
        break;
    case SCR_DIR_RLTB:
        xoffset += res_hor - lcd_handle->offset_hor - lcd_handle->original_width;
        yoffset += lcd_handle->offset_ver;
        break;
    case SCR_DIR_RLBT:
        xoffset = res_hor - lcd_handle->offset_hor - lcd_handle->original_width;
        yoffset = res_ver - lcd_handle->offset_ver - lcd_handle->original_height;
        break;
    case SCR_DIR_TBLR:
        xoffset = lcd_handle->offset_ver;
        yoffset = lcd_handle->offset_hor;
        break;
    case SCR_DIR_BTLR:
        yoffset = lcd_handle->offset_hor;
        xoffset = res_ver - lcd_handle->offset_ver - lcd_handle->original_height;
        break;
    case SCR_DIR_TBRL:
        yoffset += res_hor - lcd_handle->offset_hor - lcd_handle->original_width;
        xoffset += lcd_handle->offset_ver;
        break;
    case SCR_DIR_BTRL:
        yoffset = res_hor - lcd_handle->offset_hor - lcd_handle->original_width;
        xoffset = res_ver - lcd_handle->offset_ver - lcd_handle->original_height;
        break;
    default: break;
    }
    XF_LOGD(TAG, "dir=%d, offset=(%d, %d)", dir, xoffset, yoffset);
    *x0 += xoffset;
    *x1 += xoffset;
    *y0 += yoffset;
    *y1 += yoffset;
}

xf_err_t lcd_set_rotation(lcd_t self, lcd_scr_dir_t dir)
{
    lcd_drive_t *lcd = (lcd_drive_t *)self;
    xf_err_t ret = XF_OK;
    uint8_t reg_data = 0;
    lcd_scr_handle_t *lcd_handle = &lcd->scr;
    reg_data &= ~MADCTL_RGB;
    if (SCR_DIR_MAX < dir) {
        dir >>= 5;
    }
    if (dir > 8) {
        XF_LOGE(TAG, "Unsupport rotate direction");
        return XF_ERR_INVALID_ARG;
    }
    switch (dir) {
    case SCR_DIR_LRTB:
        lcd_handle->width = lcd_handle->original_width;
        lcd_handle->height = lcd_handle->original_height;
        break;
    case SCR_DIR_LRBT:
        reg_data |= MADCTL_MY;
        lcd_handle->width = lcd_handle->original_width;
        lcd_handle->height = lcd_handle->original_height;
        break;
    case SCR_DIR_RLTB:
        reg_data |= MADCTL_MX;
        lcd_handle->width = lcd_handle->original_width;
        lcd_handle->height = lcd_handle->original_height;
        break;
    case SCR_DIR_RLBT:
        reg_data |= MADCTL_MX | MADCTL_MY;
        lcd_handle->width = lcd_handle->original_width;
        lcd_handle->height = lcd_handle->original_height;
        break;

    case SCR_DIR_TBLR:
        reg_data |= MADCTL_MV;
        lcd_handle->width = lcd_handle->original_height;
        lcd_handle->height = lcd_handle->original_width;
        break;
    case SCR_DIR_BTLR:
        reg_data |= MADCTL_MY | MADCTL_MV;
        lcd_handle->width = lcd_handle->original_height;
        lcd_handle->height = lcd_handle->original_width;
        break;
    case SCR_DIR_TBRL:
        reg_data |= MADCTL_MX | MADCTL_MV;
        lcd_handle->width = lcd_handle->original_height;
        lcd_handle->height = lcd_handle->original_width;
        break;
    case SCR_DIR_BTRL:
        reg_data |= MADCTL_MX | MADCTL_MY | MADCTL_MV;
        lcd_handle->width = lcd_handle->original_height;
        lcd_handle->height = lcd_handle->original_width;
        break;
    default: break;
    }
    XF_LOGI(TAG, "MADCTL=%x", reg_data);
    uint8_t reg_cmd = LCD_MADCTL;
    ret |= LCD_WRITE_CMD(self, &reg_cmd, 1);
    ret |= LCD_WRITE_DATA(self, &reg_data, 1);
    if (ret != XF_OK) {
        XF_LOGE(TAG, "set rotation failed!");
        return ret;
    }
    lcd_handle->dir = dir;

    return XF_OK;
}

xf_err_t lcd_set_invert(lcd_t self, bool is_invert)
{
    return LCD_WRITE_CMD(self, is_invert ? LCD_INVON : LCD_INVOFF, 1);
}

lcd_drive_in_t *lcd_get_in(lcd_t self)
{
    lcd_drive_t *lcd = (lcd_drive_t *)self;
    return lcd->in;
}

/* ==================== [Static Functions] ================================== */

#define LCD_REG_EXTERN
#include "lcd_drive_rule.h"

static void controller_drive_reg(void)
{

#define LCD_REG_FUNCTION
#include "lcd_drive_rule.h"

}
